package cz.vse.po1715.lode.net.klient;

import cz.vse.po1715.lode.net.obecne.FrontaUdálostí;
import cz.vse.po1715.lode.net.obecne.NIOUtils;
import cz.vse.po1715.lode.net.obecne.IHerniUdalost;
import cz.vse.po1715.lode.net.obecne.Konstanty;
import java.nio.*;
import java.nio.channels.*;
import java.net.*;
import java.io.*;
import java.util.logging.*;

/*******************************************************************************
 * Instance třídy {@code AKlient} představují ...
 *
 * @author    Dominik KOSTKA
 * @version   0.00.000
 */
public abstract class AKlient extends Thread
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================
//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================

    /** logger */
    protected static Logger log = Logger.getLogger("Klient");
//== STATICKÝ INICIALIZAČNÍ BLOK - STATICKÝ KONSTRUKTOR ========================
//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================
//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================

    /** adresa serveru */
    protected InetAddress adresaServeru;

    /** připojení k serveru */
    protected SocketChannel kanál;

    /** fronta příchozích událostí */
    protected FrontaUdálostí příchozíFronta;

    /** fronta odchozích událostí */
    protected FrontaUdálostí odchozíFronta;

    /** načítač, který načítá události ze serveru */
    protected NIONačítačUdálostí načítač;

    /** buffer pro odchozí události */
    protected ByteBuffer writeBuffer;

    /** id našeho hráče */
    protected String idHrac;

    /** id současného soupeře */
    protected String idSoupeř;

    /** příznak toho, jestli je hráč ve hře */
    protected boolean veHře = false;

    /** příznak běžícího klienta */
    protected boolean běžící = true;
//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ TŘÍDY ========================================
//== OSTATNÍ NESOUKROMÉ METODY TŘÍDY ===========================================

//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================

    /***************************************************************************
     *
     */
    public AKlient()
    {
    }



//== ABSTRAKTNÍ METODY =========================================================

    /**
     * potomci musí implementovat tuto metodu, aby mohli poskytnout jméno hry
     */
    public abstract String getNázevHry();

    /**
     * potomci musí implementovat tuto tovární metodu
     */
    public abstract IHerniUdalost vytvořHerníUdálost();

    /**
     * a tuto metodu pro vytvoření specificické přihlašovací události hry
     */
    public abstract IHerniUdalost vytvořPřipojovacíUdálost();

    /**
     * a tuto metodu pro vytvoření specifické odpojovací události hry
     */
    public abstract IHerniUdalost vytvořOdpojovacíUdálost(String důvod);

    /**
     *  příchozí herní události z fronty událostí
     */
    protected abstract void zpracujPříchozíUdálost();

//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ INSTANCÍ =====================================

    /**
     * provádí určité inicializace
     */
    public void inicializuj(String args[]) {
	příchozíFronta = new FrontaUdálostí("Klient-vstup");
	odchozíFronta = new FrontaUdálostí("Klient-výstup");
	writeBuffer = ByteBuffer.allocate(Konstanty.MAX_VELIKOST_UDÁLOSTI );

	try {
	    adresaServeru = InetAddress.getByName(args[0]);
	}
	catch (UnknownHostException uhe) {
	    log.info("neznamy hostitel: " + args[0]);
	    System.exit(1);
	}

        this.idHrac = args[1];
//        IHráč hráč;
//
//        if((hráč = Server.getHráčPodleId(idHrac)) != null){
//            log.info("Hráč se zadaným ID už je k serveru připojen.");
//            System.exit(1);
//        }
        
	// připojení klienta k serveru
	if (!připoj()){
	    System.exit(1);
        }else{
            log.info("Jste připojen na server: " + idHrac);
        }

	// zapne náš síťový načítač ze serveru
	načítač = new NIONačítačUdálostí(this, kanál, příchozíFronta);
	načítač.start();

    }

//== OSTATNÍ NESOUKROMÉ METODY INSTANCÍ ========================================

    @Override
    public void run() {
	// odeslání připojovací události na server
	odešliPřipojovacíUdálost();

	// čekání na připojovací akci (hack)
	spánekVlákna(200L);

	// hlavní cyklus
	while(běžící) {
	    zpracujPříchozíUdálost();
	    zapišOdchozíUdálosti();

	    // vezme si krátkou přestávku
	    //když se nic neděje
	    spánekVlákna(50);
	}
    }

    public void stdVystup(String str) {
	if ((str != null) && !str.equals(""))
	    System.out.println("\n" + str);
	if (veHře)
	    System.out.print( idHrac + " vs. " + idSoupeř + " > " );
	else
	    System.out.print(idHrac + " > " );

    }

    public void stdChybovýVýstup(String str) {
	System.err.println("\n" + str);
    }

    public void setSoupeř(String idSoupeř) {
	this.idSoupeř = idSoupeř;
    }
 
    /**
     * připojení k serveru
     */
    protected boolean připoj() {
	log.info("Připojování na server");
	try {
	    // otevře socket kanál
	    kanál = SocketChannel.open(new InetSocketAddress
                    (adresaServeru, Konstanty.PORT));
	    kanál.configureBlocking(false);

 	    kanál.socket().setTcpNoDelay(true);
	    return true;
	}
	catch (ConnectException ce) {
	    log.info("Connect Exception: " + ce.getMessage());
	    return false;
	}
	catch (Exception e) {
	    log.info("Exception while connecting");
	    return false;
	}
    }

    /**
     * vytvoří a pošle připojovací událost na server
     */
    protected void odešliPřipojovacíUdálost() {
	IHerniUdalost událost = vytvořPřipojovacíUdálost();
	zapišUdálost(událost);
    }

    /**
     * vypne klienta
     * zastaví náš načítač a zavře klientský kanál
     */
    protected void vypni() {
	běžící = false;
	načítač.vypni();
//        consoleReader.vypni();
	try {
	    kanál.close();
	}
	catch (IOException ioe) {
	    log.info("exception while closing channel");
	}
    }

    /**
     * zapíše událost do Socket kanálu a odešle na server
     */
    protected void zapišUdálost(IHerniUdalost událost) {
	// nastaví jméno hry (pokud existuje) a id hráče
	událost.setNázevHry(getNázevHry());
	událost.setIdHráče(idHrac);

	NIOUtils.pripravBuffer(událost, writeBuffer);
	NIOUtils.zapišDoKanálu(kanál, writeBuffer);
    }

//== SOUKROMÉ A POMOCNÉ METODY TŘÍDY ===========================================
//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================

    /**
     * zapíše všechny události čekající v odchozí frontě do kanálu
     */
    private void zapišOdchozíUdálosti() {
	IHerniUdalost odchozíUdálost;
	while (odchozíFronta.getPocet() > 0) {
	    try {
		odchozíUdálost = odchozíFronta.vyjmi();
		zapišUdálost(odchozíUdálost);
	    }
	    catch (InterruptedException ie) {}
	}
    }

    /**
     * pomocná metoda k volání metody Thread.sleep()
     */
    private void spánekVlákna(long time) {
	try {
	    Thread.sleep(time);
	}
	catch(InterruptedException e) {}
    }

//== INTERNÍ DATOVÉ TYPY =======================================================
//== TESTOVACÍ METODY A TŘÍDY ==================================================
//
//    /***************************************************************************
//     * Testovací metoda.
//     */
//    public static void test()
//    {
//        AKlient inst = new AKlient();
//    }
//    /** @param args Parametry příkazového řádku - nepoužívané. */
//    public static void main( String[] args )  {  test();  }
}
